import datetime
import json
import os
import paramiko
import pysftp
import io

from typing import List

from PySide2.QtGui import QIntValidator
from PySide2.QtWidgets import *
from PySide2.QtCore import QObject, QRect, Qt

from lib import CompatibleCRC32 as CRC32, Publish
from lib.ExcelLibDefinitionMain import ExcelLibDefinitionMain
from lib.ExcelLibDefinitionTable import ExcelLibDefinitionTable

import Logger
import Util
from ExcelData import EData, SaveType, SyncJsonDir, JsonListVersionData, TableData, SyncPublishServer, FtpPublishServer
from UiPublish import Ui_PublishDialog
from dialog.SettingDialog import SettingDialog

COL_PBSERVER: [] = ["", "识别标识", "服务器名", "服务器地址"]
COL_PBSERVER_WDTH: [] = [30, 60, 120, 260]
COL_PBSERVER_ENABLED: int = 0
COL_PBSERVER_TAG: int = 1
COL_PBSERVER_CAPTION: int = 2
COL_PBSERVER_PATH: int = 3

COL_DIR: [] = ["", "文件名", "生成者", "MD5", "新", "导出日期"]
COL_DIR_WDTH: [] = [30, 170, 120, 180, 30, 160]
COL_DIR_ENABLED: int = 0
COL_DIR_FILENAME: int = 1
COL_DIR_AUTHOR: int = 2
COL_DIR_MD5: int = 3
COL_DIR_ISNEW: int = 4
COL_DIR_DATE: int = 5

COL_PBFTP: [] = ["唯一标识", "服务器地址", "账户", "远端路径"]
COL_PBFTP_WDTH: [] = [120, 200, 150, 200]
COL_PBFTP_CAPTION: int = 0
COL_PBFTP_PATH: int = 1
COL_PBFTP_USER: int = 2
COL_PBFTP_DIR: int = 3


class PublishDialog(QWidget, Ui_PublishDialog):
    edata: EData
    logger: Logger
    settingDialog: SettingDialog = None

    currentSelectedPath: str

    def __init__(self):
        super(PublishDialog, self).__init__()
        self.edata = EData()
        self.logger = Logger.Logger()

        self.ui = Ui_PublishDialog()
        self.ui.setupUi(self)

        # 绑定事件
        self.ui.btnSetting.clicked.connect(self.onBtnSettingClick)
        self.ui.btnSelectModi.clicked.connect(self.onBtnSelectModiClick)
        self.ui.btnSelectAll.clicked.connect(self.onBtnSelectAllClick)
        self.ui.btnPublishJson.clicked.connect(self.onBtnPublishJsonClick)
        self.ui.btnSelectNone.clicked.connect(self.onBtnSelectNoneClick)
        self.ui.btnClose.clicked.connect(self.onBtnCloseClick)

        self.ui.btnSave.clicked.connect(self.onBtnSaveClick)

        self.ui.btnFtpAdd.clicked.connect(self.onBtnFtpAddClick)
        self.ui.btnFtpDel.clicked.connect(self.onBtnFtpDelClick)

        self.ui.btnPublishFtp.clicked.connect(self.onBtnPublishFtpClick)
        self.ui.btnPublishSave.clicked.connect(self.onPublishSaveClick)
        self.ui.btnFtpGenConfig.clicked.connect(self.onBtnFtpGenConfigClick)
        # chkClearOldJson
        self.ui.listFtpServer.itemClicked.connect(self.onListFtpServerClick)
        # self.ui.listFtpServer.itemChanged.connect(self.onListFtpServerChange)
        # self.ui.listFtpServer.itemDoubleClicked.connect(self.onListFtpServerDoubleClicked)

        validator = QIntValidator(0, 99999)
        self.ui.txtFtpVer.setValidator(validator)
        validator2 = QIntValidator(1, 999999)
        self.ui.txtFtpPort.setValidator(validator2)
        validator3 = QIntValidator(0, 99999)
        self.ui.txtFtpResVer.setValidator(validator3)

        self.ui.txtFtpVer.textChanged.connect(self.onTxtFtpVerChanged)
        self.ui.txtFtpResVer.textChanged.connect(self.onTxtFtpResVerChanged)

        self.ui.btnCheckFtpVer.clicked.connect(self.onBtnCheckFtpVerClick)

        self.ui.listJson.itemClicked.connect(self.onListFileClick)  # 点击列表Item事件
        self.ui.listServer.itemClicked.connect(self.onlistServerClick)  # 点击列表Item事件

        # 设置表头样式
        self.initColumnListFile()
        self.initColumnlistServer()
        self.initColumnlistFtpServer()

    def show(self, currentSelectedPath: str, selVer: JsonListVersionData):
        print("打开发布UI")
        self.currentSelectedPath = currentSelectedPath
        self.loadFromData()
        super(PublishDialog, self).show()
        # self.resetFormPosition()
        # 刷新列表
        # self.refreshTableWidgetJson()
        self.refreshListFile(selVer)
        self.refreshListServer()
        self.refreshListFtpServer()
        self.resetFormPosition()
        self.loadFromData()

    isFirstShow: bool = True

    def loadFromData(self):
        self.ui.txtUsername.setText(self.edata.authData.username)
        self.ui.txtPassword.setText(self.edata.authData.password)
        self.ui.chkClearOldJson.setChecked(self.edata.authData.isClearOldPublishJson)
        self.ui.txtFtpCertPath.setText(self.edata.authData.ftpCretPath)
        self.ui.txtFtpUsername.setText(self.edata.authData.ftpUsername)
        self.ui.txtFtpPassword.setText(self.edata.authData.ftpPassword)

    def saveFromData(self):
        self.edata.authData.isClearOldPublishJson = self.ui.chkClearOldJson.isChecked()
        self.edata.authData.ftpCretPath = self.ui.txtFtpCertPath.text()
        self.edata.authData.ftpUsername = self.ui.txtFtpUsername.text()
        self.edata.authData.ftpPassword = self.ui.txtFtpPassword.text()
        self.edata.save(SaveType.Auth)

    def resetFormPosition(self):
        # 调整窗口位置，不阻挡logs
        # if (not self.isVisible()) and self.isHidden():
        #     Util.setToTop(self)

        if self.isFirstShow:
            # pyside2奇怪的bug，第一次打开坐标不准，第二次才准，所以只能用标记处理第一次打开的坐标
            self.isFirstShow = False
            self.move(Util.mainForm.x(), Util.mainForm.y())
        else:
            mainRect: QRect = Util.mainForm.geometry()
            self.move(mainRect.x(), mainRect.y())
            self.setFixedWidth(mainRect.width())
        # self.logger.resetFormPosition(self, True)

    def onBtnCloseClick(self):
        """按钮: 关闭"""
        print("点击关闭按钮关闭窗口")
        self.saveFromData()
        self.edata.save(SaveType.Auth)
        self.close()

    def onBtnSaveClick(self):
        """保存账号密码"""
        self.saveFromData()
        self.edata.authData.username = self.ui.txtUsername.text()
        self.edata.authData.password = self.ui.txtPassword.text()
        self.edata.save(SaveType.Auth)

    def closeEvent(self, event):
        print("点击X关闭窗口")
        self.saveFromData()
        self.edata.save(SaveType.Auth)

    def setRunningStatus(self, isRunning: bool):
        if isRunning:
            self.setAllButtonsEnabled(self, False)
        else:
            self.setAllButtonsEnabled(self, True)

    def setAllButtonsEnabled(self, obj: QObject, enabled: bool):
        """递归找到按钮操作Enabled"""
        if obj is None:
            return
        for child in obj.children():
            if type(child) is QPushButton:
                button: QPushButton = child
                # print("找到按钮：" + button.text())
                button.setEnabled(enabled)
            else:
                self.setAllButtonsEnabled(child, enabled)

    ####################################################################
    #   【发布列表】相关
    ####################################################################

    def initColumnListFile(self):
        # 设置表头样式
        self.ui.listJson.setColumnCount(len(COL_DIR))
        self.ui.listJson.setHorizontalHeaderLabels(COL_DIR)
        self.ui.listJson.horizontalHeader().setStyleSheet('''
            ::section {
                background-color: #FFEE99;
                border-style: solid;
                border-color: #BBBBBB;
                border-width: 1px;
                padding: 0px 0px;
                font-weight:bold;
                }''')
        for i in range(0, len(COL_DIR_WDTH)):
            self.ui.listJson.setColumnWidth(i, COL_DIR_WDTH[i])
        self.ui.listJson.horizontalHeader().setStretchLastSection(True)

    def refreshListFile(self, ver: JsonListVersionData):
        """刷新左边【发布目录】表格数据"""
        if not Util.isDirAndExists(ver.path):
            self.logger.logWarning("你选择的数据包版本目录数据错误！")
            return

        definitionFn: str = ver.path + self.edata.setting.defDefineJsonFile
        if not Util.isFileAndExists(definitionFn):
            self.logger.logWarning("你选择的数据包_definition.json数据错误！")
            return
        with open(definitionFn, 'r', encoding='utf8') as f:
            jsonData = json.load(f)
        if jsonData is None:
            self.logger.logWarning("你选择的数据包_definition.json数据读取错误！")
            return
        headData: dict = jsonData.get("header")
        if headData is None:
            headData: dict = dict()
            jsonData["header"] = headData
        dfVersion = headData["version"]
        dfDate = headData["date"]
        dfAuthor = headData["author"]
        loadconfigs: [] = jsonData.get("defines")

        tablesData: List[TableData] = None
        tmpTablesData = jsonData.get("tables")
        if type(tmpTablesData) is list:
            tablesData = tmpTablesData

        fileList: List[str] = os.listdir(ver.path)
        listLen = len(fileList)
        if listLen > 1:
            listLen -= 1;
        self.ui.listJson.setRowCount(listLen)
        count: int = 0
        for fn in fileList:
            if fn == self.edata.setting.defDefineJsonFile:  # 不显示definition文件
                continue
            fPath = os.path.join(ver.path, fn)
            jsonName: str = os.path.splitext(fn)[0]
            itemChecked = QTableWidgetItem("")
            itemChecked.setCheckState(Qt.Unchecked)
            itemFileName = QTableWidgetItem(fn)
            isnew: str = ""
            md5: str = ""
            author: str = ""
            strTime: str = ""
            for i in range(0, len(tablesData)):
                oldTbData = tablesData[i]
                if oldTbData["jsonName"] == jsonName:
                    md5 = oldTbData["md5"]
                    author = oldTbData["author"]
                    strTime = oldTbData["date"]
                    version = oldTbData["version"]
                    if version == dfVersion:
                        isnew = "√"
                        itemChecked.setCheckState(Qt.Checked)

            itemMd5 = QTableWidgetItem(md5)
            itemNew = QTableWidgetItem(isnew)
            itemNew.setTextAlignment(Qt.AlignCenter)
            itemAuthor = QTableWidgetItem(author)
            itemTime = QTableWidgetItem(strTime)

            self.ui.listJson.setItem(count, COL_DIR_ENABLED, itemChecked)
            self.ui.listJson.setItem(count, COL_DIR_FILENAME, itemFileName)
            self.ui.listJson.setItem(count, COL_DIR_AUTHOR, itemAuthor)
            self.ui.listJson.setItem(count, COL_DIR_MD5, itemMd5)
            self.ui.listJson.setItem(count, COL_DIR_ISNEW, itemNew)
            self.ui.listJson.setItem(count, COL_DIR_DATE, itemTime)
            count += 1

    def onListFileClick(self, item: QTableWidgetItem):
        """点击左边【发布目录】表格数据"""
        print("【发布目录】QTable 您点击了：" + item.text() + " row:" + str(item.row()))
        # self.syncCheckStateCopyDir()  # 同步勾选状态

    ####################################################################
    #   【版本Version列表】相关
    ####################################################################

    releaseVerData: JsonListVersionData = None
    verDataList: List[JsonListVersionData] = None

    def initColumnlistServer(self):
        # 设置专用服务器表头样式
        self.ui.listServer.setColumnCount(len(COL_PBSERVER))
        self.ui.listServer.setHorizontalHeaderLabels(COL_PBSERVER)
        self.ui.listServer.horizontalHeader().setStyleSheet('''
            ::section {
                background-color: #FFEE99;
                border-style: solid;
                border-color: #BBBBBB;
                border-width: 1px;
                padding: 0px 0px;
                font-weight: bold;
                text-align: left;
                }''')
        for i in range(0, len(COL_PBSERVER_WDTH)):
            self.ui.listServer.setColumnWidth(i, COL_PBSERVER_WDTH[i])
        self.ui.listServer.horizontalHeader().setStretchLastSection(True)

    def initColumnlistFtpServer(self):
        # 设置FTP服务器表头样式
        self.ui.listFtpServer.setColumnCount(len(COL_PBFTP))
        self.ui.listFtpServer.setHorizontalHeaderLabels(COL_PBFTP)
        self.ui.listFtpServer.horizontalHeader().setStyleSheet('''
            ::section {
                background-color: #FFEE99;
                border-style: solid;
                border-color: #BBBBBB;
                border-width: 1px;
                padding: 0px 0px;
                font-weight: bold;
                text-align: left;
                }''')
        for i in range(0, len(COL_PBFTP_WDTH)):
            self.ui.listFtpServer.setColumnWidth(i, COL_PBFTP_WDTH[i])
        self.ui.listFtpServer.horizontalHeader().setStretchLastSection(True)

    def refreshListServer(self):
        """下方【发布服务器】表格数据"""
        self.refreshingServerList = True
        self.ui.listServer.setRowCount(len(self.edata.setting.syncServers))
        count: int = 0
        for (fKey, fData1) in self.edata.setting.syncServers.items():
            fData: SyncPublishServer = fData1
            # 插入单选框-启用
            itemEnabled = QTableWidgetItem("")
            itemEnabled.setCheckState(Qt.Unchecked)
            # if fData.isEnabledChecked:
            #     itemEnabled.setCheckState(Qt.Checked)
            # else:
            #     itemEnabled.setCheckState(Qt.Unchecked)
            # itemEnabled.setTextAlignment(Qt.AlignCenter)

            # 服务器名
            itemCaption: QTableWidgetItem = QTableWidgetItem(fData.caption)
            # 服务器地址
            itemPath: QTableWidgetItem = QTableWidgetItem(fData.serverPath)
            # 服务器名
            itemTag: QTableWidgetItem = QTableWidgetItem(fData.tag)

            self.ui.listServer.setItem(count, COL_PBSERVER_ENABLED, itemEnabled)
            self.ui.listServer.setItem(count, COL_PBSERVER_CAPTION, itemCaption)
            self.ui.listServer.setItem(count, COL_PBSERVER_PATH, itemPath)
            self.ui.listServer.setItem(count, COL_PBSERVER_TAG, itemTag)
            count += 1
        self.refreshingServerList = False

    def onlistServerClick(self, item: QTableWidgetItem):
        """点击右边【版本列表】表格数据"""
        print("【版本列表】QTable 您点击了：" + item.text() + " row:" + str(item.row()))
        verItem: QTableWidgetItem = self.ui.listServer.item(item.row(), COL_PBSERVER_TAG)
        selectedItem: SyncPublishServer = None
        for key, value in self.edata.setting.syncServers.items():
            if value.tag == verItem.text():
                selectedItem = value

        if selectedItem == None:
            QMessageBox.warning(self, "提示", "选择的服务器有误")
            return

        for i in range(0, self.ui.listServer.rowCount()):
            tagItem: QTableWidgetItem = self.ui.listServer.item(i, COL_PBSERVER_TAG)
            checkboxItem: QTableWidgetItem = self.ui.listServer.item(i, COL_PBSERVER_ENABLED)
            if verItem.text() == tagItem.text():
                if checkboxItem.checkState() == Qt.Checked:
                    checkboxItem.setCheckState(Qt.Unchecked)
                else:
                    checkboxItem.setCheckState(Qt.Checked)
            # if verItem.text() == tagItem.text():
            #     checkboxItem.setCheckState(Qt.Checked)
            # else:
            #     checkboxItem.setCheckState(Qt.Unchecked)

        self.logger.logNotice("选择发布服务器：[" + selectedItem.caption + "]")

    def onBtnSettingClick(self):
        """按钮：打开配置"""
        if self.settingDialog == None:
            self.settingDialog = SettingDialog()
        self.settingDialog.show()

    def onBtnSelectAllClick(self):
        """按钮: 全选"""
        for i in range(0, self.ui.listJson.rowCount()):
            checkbox: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_ENABLED)
            checkbox.setCheckState(Qt.Checked)
        self.ui.chkClearOldJson.setChecked(True)

    def onBtnSelectNoneClick(self):
        """按钮: 全不选"""
        for i in range(0, self.ui.listJson.rowCount()):
            checkbox: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_ENABLED)
            checkbox.setCheckState(Qt.Unchecked)
        self.ui.chkClearOldJson.setChecked(False)

    def onBtnSelectModiClick(self):
        """按钮: 选变更"""
        for i in range(0, self.ui.listJson.rowCount()):
            isNewItem: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_ISNEW)
            checkbox: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_ENABLED)
            if isNewItem.text() == "":
                checkbox.setCheckState(Qt.Unchecked)
            else:
                checkbox.setCheckState(Qt.Checked)
        self.ui.chkClearOldJson.setChecked(False)

    def onBtnPublishJsonClick(self):
        self.setRunningStatus(True)
        self.publicJson()
        self.setRunningStatus(False)

    def publicJson(self):
        """按钮: [数据包列表]发布选中数据包"""
        # 获得需要发布的数据包
        definition: ExcelLibDefinitionMain = Util.getDefinition(self.currentSelectedPath)
        if definition is None:
            self.logger.logError("你所选的不是有效数据包：" + self.currentSelectedPath)
            return

        # 获得选择了的服务器
        selectedServers: List[SyncPublishServer] = []
        for i in range(0, self.ui.listServer.rowCount()):
            tagItem: QTableWidgetItem = self.ui.listServer.item(i, COL_PBSERVER_TAG)
            checkboxItem: QTableWidgetItem = self.ui.listServer.item(i, COL_PBSERVER_ENABLED)
            if checkboxItem.checkState() == Qt.Checked:
                for key, value in self.edata.setting.syncServers.items():
                    if value.tag == tagItem.text():
                        selectedServers.append(value)

        # 检查是否选择了服务器
        if len(selectedServers) < 1:
            QMessageBox.warning(self, "提示", "请选择你发布的服务器")
            return

        username = self.ui.txtUsername.text()
        passcode = self.ui.txtPassword.text()

        for i in range(0, len(selectedServers)):
            self.logger.logNotice("开始提交数据到服务 -> [ " + selectedServers[i].caption + " ]")
            selectedServers[i].isSubmitOk = self.publishServer(definition, selectedServers[i], username, passcode)

        self.logger.logSuccess("---- 提交结果清单 ----")
        for i in range(0, len(selectedServers)):
            spaces = ' ' * max(0, 20 - len(selectedServers[i].caption))
            if selectedServers[i].isSubmitOk:
                self.logger.logSuccess("[ " + selectedServers[i].caption + " ]" + spaces + "\t--> [√] 成功")
            else:
                self.logger.logError("[ " + selectedServers[i].caption + " ]" + spaces + "\t--> [×] 失败")

        # passcode = CRC32.get32BitHex(passcode + "|" + selectedServer.tag)
        # print(selectedServer.tag, username, passcode)

    def publishServer(self, definition: ExcelLibDefinitionMain, selectedServer: SyncPublishServer, username: str,
                      passcode: str):

        caption = selectedServer.caption
        host = selectedServer.serverPath
        tag = selectedServer.tag

        # 弹出输入密码
        if passcode == "":
            pw, isOk = QInputDialog.getText(self, "[ " + caption + " ] 需要输入密码!", "密码：", QLineEdit.Normal, "")
            if isOk == False:
                return False
            if pw == "":
                QMessageBox.warning(self, "提示", "必须输入密码")
                return False
            else:
                passcode = pw

        # 登陆发布服务器
        loginResult: dict = Publish.loginConfigServer(host, tag, username, passcode)  # 临时账号密码
        # 检查返回内容并输出错误
        if self.checkAndLoggerHttpResult(loginResult) < 0:
            return False
        token: str = loginResult["token"]

        # 开始提交definition
        jsonData: {} = None
        definitionFileName = self.edata.setting.defDefineJsonFile
        definePath = os.path.join(self.currentSelectedPath, definitionFileName)
        result = None
        if os.path.isfile(definePath):
            jsonText: str = None
            with open(definePath, 'r', encoding='utf8') as f:
                jsonText = f.read()
            if jsonText is None:
                self.logger.logError("文件读取错误：" + definePath)
            else:
                result = Publish.postDefinition(host, tag, token, definition.version, jsonText)
                # 检查返回内容并输出错误
                if self.checkAndLoggerHttpResult(result) < 0:
                    self.logger.logError("definition.json提交失败！")
                    return False
        else:
            self.logger.logError("definition.json数据文件不存在！")
            return False

        totalSize: int = 0  # 最终提交多少个json表

        # 提交单个json数据表
        for item in os.listdir(self.currentSelectedPath):
            isChecked: bool = self.checkJsonFileChecked(item)
            if isChecked == False:
                continue
            tableDefine: ExcelLibDefinitionTable = None
            for table in definition.tables:
                if table.jsonName + ".json" == item:
                    tableDefine = table
            if tableDefine is None:
                continue
            jsonPath = os.path.join(self.currentSelectedPath, item)
            if os.path.isfile(jsonPath):  # 判断文件是否存在
                if item == self.edata.setting.defDefineJsonFile:
                    # definition.json不提交
                    continue
                jsonText: str = None
                with open(jsonPath, 'r', encoding='utf8') as f:
                    jsonText = f.read()
                if jsonText is None:
                    self.logger.logError("文件读取错误：" + jsonPath)
                else:
                    result = Publish.postTable(host, tag, token, item, definition.version, jsonText)
                    totalSize += 1
                    # 检查返回内容并输出错误
                    if self.checkAndLoggerHttpResult(result) < 0:
                        return False

                self.logger.logNotice("提交服务器表数据文件：" + item)

        isClearOldTable: bool = self.ui.chkClearOldJson.isChecked()
        result = Publish.publishData(host, tag, token, 1, isClearOldTable, totalSize)
        self.logger.logSpace()
        self.logger.logNotice("本次一共提交文件数：" + str(totalSize) + "  清理旧表：" + str(isClearOldTable))
        if self.checkAndLoggerHttpResult(result) < 0:
            return False
        self.logger.logAllDone("提交服务器表数据完成")

        # 发布后验证数据
        chechResult: dict = Publish.getLastVersion(host, tag)
        if chechResult is None:
            self.logger.logWarning("复检错误！请手动检查上传后数据")
            return False
        version = chechResult.get("version")
        author = chechResult.get("author")
        md5 = chechResult.get("md5")
        count = chechResult.get("count")
        self.logger.logNotice("复检结果，当前线上服务器表数据：")
        self.logger.logNotice("版本：" + version)
        self.logger.logNotice("CRC：" + md5)
        self.logger.logNotice("发布者：" + author)
        self.logger.logNotice("数据表总数：" + str(count))

        # currentSelectedPath: str = self.getSelectedItemPath()
        # if currentSelectedPath is None:
        #     return
        # if not Util.isDirAndExists(currentSelectedPath):
        #     QMessageBox.warning(self, "提示", "选择的数据包并没有数据")
        #     return
        return True

    def checkJsonFileChecked(self, jsonFileName: str):
        """检查json文件是否被勾选"""
        for i in range(0, self.ui.listJson.rowCount()):
            enable: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_FILENAME)
            checkbox: QTableWidgetItem = self.ui.listJson.item(i, COL_DIR_ENABLED)
            if enable.text() == jsonFileName:
                if checkbox.checkState() == Qt.Checked:
                    return True
                else:
                    return False
        return False

    #########################
    # FTP发布相关
    #########################
    selectedOldServerKey: str = ""

    def getFtpDataError(self):
        error = ""
        if self.ui.txtFtpIp.text() == "":
            error += "\n IP不能为空"
        if self.ui.txtFtpPort.text() == "":
            error += "\n 端口不能为空"
        if self.ui.txtFtpUsername.text() == "":
            error += "\n 用户名不能为空"
        if self.ui.txtFtpPassword.text() == "":
            error += "\n 密码不能为空"
        if self.ui.txtFtpRemoteDir.text() == "":
            error += "\n 远程路径不能为空"
        if self.ui.txtFtpCertPath.text() == "":
            error += "\n 证书不能为空"
        return error

    def onBtnFtpAddClick(self):
        """添加一个FTP服务器"""
        error = self.getFtpDataError()
        if error != "":
            QMessageBox.warning(self, "提示", error)
            return

        # 开始添加一个服务器列表内容
        enter, ok = QInputDialog.getText(self, "起名", "请为配置改名，服务器端识别用", QLineEdit.EchoMode.Normal,
                                         "新建名字")
        print(enter)
        print(ok)
        if (ok == True):
            if (enter == "新建名字"):
                QMessageBox.warning(self, "提示", "请修改名字！")
                return
            if (self.getFtpServerDictItemByKey(enter) != None):
                QMessageBox.warning(self, "提示", "名字重复率！")
                return

            data: FtpPublishServer = FtpPublishServer()
            data.caption = enter
            data.ip = self.ui.txtFtpIp.text()
            data.port = self.ui.txtFtpPort.text()
            data.username = self.ui.txtFtpUsername.text()
            data.password = self.ui.txtFtpPassword.text()
            data.remoteDir = self.ui.txtFtpRemoteDir.text()
            data.certPath = self.ui.txtFtpCertPath.text()
            self.edata.setting.ftpServers[data.caption] = data
            self.saveFtpServers()
            # 刷新列表
            self.refreshListFtpServer()
            print("添加一个新FTP发布服务器：" + data.caption);

        pass

    def onBtnFtpDelClick(self):
        """按钮: [发布服务器]删除选中"""
        print(self.ui.listServer.currentRow())
        if self.ui.listServer.currentRow() < 0:
            QMessageBox.warning(self, "提示", "你还没有选择要删除的同步服务器配置！")
            return
        keyItem: QTableWidgetItem = self.ui.listServer.item(self.ui.listServer.currentRow(), COL_PBSERVER_CAPTION)
        syncPbServer: SyncPublishServer = self.edata.setting.syncServers.get(keyItem.text())
        if not syncPbServer is None:
            del self.edata.setting.syncServers[keyItem.text()]
        self.refreshListServer()

    def onBtnPublishFtpClick(self):
        self.setRunningStatus(True)
        self.publicFtp()
        self.setRunningStatus(False)

    def onBtnCheckFtpVerClick(self):
        error = self.getFtpDataError()
        if error != "":
            QMessageBox.warning(self, "提示", error)
            return

        # SFTP连接配置
        hostname = self.ui.txtFtpIp.text()  # 'example.com'  # SFTP服务器地址
        port = int(self.ui.txtFtpPort.text())  # 22  # 默认的 SFTP 端口是 22

        if port is None:
            QMessageBox.warning(self, "提示", "端口格式错误！")
            return
        username = self.ui.txtFtpUsername.text()  # 'your_username'  # SSH登录用户名
        private_key_path = self.ui.txtFtpCertPath.text()  # '/path/to/your/private_key'  # 本地私钥文件路径
        setting_file = self.ui.labConfigName.text()
        local_dir = 'output/ftp'
        remote_dir = self.ui.txtFtpRemoteDir.text()

        # 判断目录是否存在，如果不存在，则创建
        if not os.path.exists(local_dir):
            os.makedirs(local_dir)

        # 检查私钥文件是否存在
        if not os.path.exists(private_key_path):
            QMessageBox.warning(self, "提示", f"私钥文件不存在：{private_key_path}")
            return

        # 创建SSH客户端对象
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            # 加载本地私钥文件（假设你有对应的私钥）
            key = paramiko.Ed25519Key.from_private_key_file(private_key_path)
            # 连接服务器，使用公钥认证
            ssh.connect(hostname=hostname, port=port, username=username, pkey=key)
            # 在SSH连接上打开一个SFTP会话
            sftp = ssh.open_sftp()
            # 进入指定目录
            sftp.chdir(remote_dir)
            # 列出远程目录中的文件
            file_list = sftp.listdir('.')
            print(file_list)
            self.ui.txtFtpLog.clear()
            # 关闭SFTP会话
            for str in file_list:
                self.ui.txtFtpLog.append(str)

            remote_file_path = remote_dir + "/" + setting_file
            local_file_path = local_dir + "/" + setting_file

            try:
                # 检查远程文件是否存在
                sftp.stat(remote_file_path)  # 如果文件存在则不会抛出异常
            except FileNotFoundError:
                QMessageBox.warning(self, "提示", f"远程文件不存在：\n {remote_file_path}")
                return
            except Exception as e:
                QMessageBox.warning(self, "错误", f"无法访问远程文件: \n {e}")
                return

            sftp.get(remote_file_path, local_file_path)
            if os.path.exists(local_file_path) and os.path.getsize(local_file_path) > 0:
                try:
                    with open(local_file_path, 'r', encoding='utf-8') as file:  # 根据实际文件编码调整encoding参数
                        content = file.read()
                        self.ui.txtFtpConfigFile.setText(content)
                        jdata = json.loads(content)
                        resVer = jdata["resVersion"]
                        version = jdata["version"]
                        self.ui.txtFtpResVer.setText(resVer)
                        self.ui.txtFtpVersion.setText(version)

                    QMessageBox.warning(self, "错误", "已有版本，已经下载，请左侧窗口检查!")
                except UnicodeDecodeError:
                    QMessageBox.warning(self, "错误", "文件编码格式可能不正确，无法正确显示内容")
                except Exception as e:
                    QMessageBox.warning(self, "错误", f"打开文件时出现其他错误:{e}")

            else:
                QMessageBox.warning(self, "提示", f"线上版本文件还没创建：{setting_file}")

            sftp.close()
        except paramiko.ssh_exception.AuthenticationException:
            QMessageBox.warning(self, "认证失败", "SSH认证失败，请检查用户名或密钥")
        except paramiko.SSHException as e:
            QMessageBox.warning(self, "SSH连接错误", f"SSH连接出现错误: {e}")
        except Exception as e:
            QMessageBox.warning(self, "其他错误", f"发生了其他错误: {e}")
        finally:
            # 关闭SSH连接
            ssh.close()

    def publicFtp(self):
        configFileContent: str = self.ui.txtFtpConfigFile.toPlainText()
        configFileContent = configFileContent.strip()
        if configFileContent == "":
            QMessageBox.warning(self, "提示", "版本文件不能为空，请先生成版本文件")
            return
        error = self.getFtpDataError()
        if error != "":
            QMessageBox.warning(self, "提示", error)
            return

        libzipName = self.ui.txtFtpZipName.text()
        if libzipName == "":
            QMessageBox.warning(self, "提示", "大版本号不能为空")
            return

        # SFTP连接配置
        hostname = self.ui.txtFtpIp.text()  # 'example.com'  # SFTP服务器地址
        port = int(self.ui.txtFtpPort.text())  # 22  # 默认的 SFTP 端口是 22
        if port is None:
            QMessageBox.warning(self, "提示", "端口格式错误！")
            return
        username = self.ui.txtFtpUsername.text()  # 'your_username'  # SSH登录用户名
        private_key_path = self.ui.txtFtpCertPath.text()  # '/path/to/your/private_key'  # 本地私钥文件路径
        setting_file = self.ui.labConfigName.text()
        local_dir = 'output/ftp'
        remote_dir = self.ui.txtFtpRemoteDir.text()

        # 检查私钥文件是否存在
        if not os.path.exists(private_key_path):
            QMessageBox.warning(self, "提示", f"私钥文件不存在：{private_key_path}")
            return

        # 创建SSH客户端对象
        ssh = paramiko.SSHClient()
        ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
        try:
            # 加载本地私钥文件（假设你有对应的私钥）
            key = paramiko.Ed25519Key.from_private_key_file(private_key_path)
            # 连接服务器，使用公钥认证
            ssh.connect(hostname=hostname, port=port, username=username, pkey=key)
            # 在SSH连接上打开一个SFTP会话
            sftp = ssh.open_sftp()
            # 进入指定目录
            sftp.chdir(remote_dir)
            # 列出远程目录中的文件
            file_list = sftp.listdir('.')
            print(file_list)
            # 关闭SFTP会话
            for str in file_list:
                self.logger.logNotice(str)

            remote_file_path = remote_dir + "/" + setting_file
            # local_file_path = local_dir + "/" + setting_file

            isRemoteHasFiles: bool = False
            try:
                # 检查远程文件是否存在
                sftp.stat(remote_file_path)  # 如果文件存在则不会抛出异常
                isRemoteHasFiles = True
            except FileNotFoundError:
                isRemoteHasFiles = False
            except Exception as e:
                pass

            if isRemoteHasFiles == True:
                # 弹出确认上传文件的询问框
                reply = QMessageBox.question(
                    self, "确认上传", f"远程已经存在该文件，是否覆盖上传文件？\n地址: {remote_dir}?",
                    QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

                if reply == QMessageBox.No:
                    QMessageBox.information(self, "取消上传", "上传已取消")
                    return  # 如果选择了取消，直接返回

            # 开始提交definition
            localDefinitionFileName = self.edata.setting.defDefineJsonFile
            remoteDefinitionFileName = self.ui.labFtpDefinition.text()
            definePath = os.path.join(self.currentSelectedPath, localDefinitionFileName)
            remote_definition_file_path = remote_dir + "/" + remoteDefinitionFileName
            if os.path.isfile(definePath):
                # 使用 sftp.put() 上传文件
                sftp.put(definePath, remote_definition_file_path)
            else:
                self.logger.logError("definition.json数据文件不存在！")
                return False

            # 开始提交definition
            localLibzipFileName = "lib.zip"
            libzipPath = os.path.join(self.currentSelectedPath, localLibzipFileName)
            remote_zip_file_path = remote_dir + "/" + libzipName
            if os.path.isfile(libzipPath):
                # 使用 sftp.put() 上传文件
                sftp.put(libzipPath, remote_zip_file_path)
            else:
                self.logger.logError("definition.json数据文件不存在！")
                return False



            # 上传文件内容（字符串）到远程指定文件名
            with sftp.open(remote_file_path, 'w') as remote_file:
                remote_file.write(configFileContent)

            QMessageBox.information(self, "上传成功", f"文件 {setting_file} 已成功上传到 {remote_dir}！")

            sftp.close()
        except paramiko.ssh_exception.AuthenticationException:
            QMessageBox.warning(self, "认证失败", "SSH认证失败，请检查用户名或密钥")
        except paramiko.SSHException as e:
            QMessageBox.warning(self, "SSH连接错误", f"SSH连接出现错误: {e}")
        except Exception as e:
            QMessageBox.warning(self, "其他错误", f"发生了其他错误: {e}")
        finally:
            # 关闭SSH连接
            ssh.close()


    templateConfig: str = """{
    "version": "#version#",
    "zipFile": "#ver#",
    "resVersion": "#resVer#"
}
    """

    def onBtnFtpGenConfigClick(self):
        version = self.ui.txtFtpVersion.text()
        zipVer = self.ui.txtFtpZipName.text()
        resVer = self.ui.txtFtpResVer.text()
        if version == "":
            QMessageBox.warning(self, "提示", "版本不能为空")
            return
        if zipVer == "":
            QMessageBox.warning(self, "提示", "大版本号不能为空")
            return

        s = self.templateConfig
        s = s.replace("#version#", version)
        s = s.replace("#ver#", zipVer)
        s = s.replace("#resVer#", resVer)
        self.ui.txtFtpConfigFile.setText(s)
        pass

    def onPublishSaveClick(self):
        error = self.getFtpDataError()
        if error != "":
            QMessageBox.warning(self, "提示", error)
            return
        caption = self.ui.txtFtpCaption.text();
        if caption == "":
            QMessageBox.warning(self, "提示", "唯一标识为空")

        data = self.edata.setting.ftpServers.get(caption)
        if data is None:
            QMessageBox.warning(self, "提示", "唯一标识并没找到")

        # data.caption = self.ui.txtFtpCaption.text()
        data.ip = self.ui.txtFtpIp.text()
        data.port = self.ui.txtFtpPort.text()
        data.username = self.ui.txtFtpUsername.text()
        data.password = self.ui.txtFtpPassword.text()
        data.certPath = self.ui.txtFtpCertPath.text()
        data.remoteDir = self.ui.txtFtpRemoteDir.text()
        data.lastVersion = self.ui.txtFtpVersion.text()
        self.edata.save(SaveType.Setting)
        self.refreshListFtpServer()

    def onListFtpServerClick(self, item: QTableWidgetItem):
        print(
            "【发布FTP服务器】QTable 您点击了：" + item.text() + " row:" + str(item.row()) + " col:" + str(item.column()))
        keyItem: QTableWidgetItem = self.ui.listFtpServer.item(item.row(), COL_PBFTP_CAPTION)
        data: FtpPublishServer = self.edata.setting.ftpServers[keyItem.text()]
        self.ui.txtFtpCaption.setText(data.caption)
        self.ui.txtFtpIp.setText(data.ip)
        self.ui.txtFtpPort.setText(data.port)
        self.ui.txtFtpUsername.setText(data.username)
        self.ui.txtFtpPassword.setText(data.password)
        self.ui.txtFtpCertPath.setText(data.certPath)
        self.ui.txtFtpRemoteDir.setText(data.remoteDir)
        self.ui.txtFtpVersion.setText(data.lastVersion)

    def onListFtpServerDoubleClicked(self, item: QTableWidgetItem):
        """点击下方【发布服务器】表格数据"""
        print("【发布FTP服务器】QTable 您双击了：" + item.text() + " row:" + str(item.row()))
        self.selectedOldServerKey = item.text();

    def onTxtFtpVerChanged(self, text):
        self.ui.labConfigName.setText("config_" + text + ".json")
        # 获取当前日期时间对象
        now = datetime.datetime.now()
        # 使用strftime方法按照指定格式格式化输出
        formatted_time = now.strftime('%y%m%d_%H%M')
        self.ui.txtFtpZipName.setText("lib_" + text + "_" + formatted_time + ".zip")
        self.ui.labFtpDefinition.setText("_definition_" + text + ".json")
        pass

    def onTxtFtpResVerChanged(self, text):
        pass

    def saveFtpServers(self):
        self.edata.save(SaveType.Setting)

    def onListFtpServerChange(self, item: QTableWidgetItem):
        print("【发布FTP服务器】QTable 更新了：" + item.text() + " row:" + str(item.row()) + " col:" + str(item.column()))
        row = item.row()
        col = item.column()

        if (self.refreshingServerList == True):
            return
        if row > self.ui.listServer.rowCount() - 1:
            QMessageBox.warning(self, "提示", "选择的数据包不在范围内！")
            return
        # 提取key (key不能重复)
        keyItem: QTableWidgetItem = self.ui.listServer.item(row, COL_PBSERVER_CAPTION)
        key = keyItem.text()
        # 提取tag (tag不能重复)
        tagItem: QTableWidgetItem = self.ui.listServer.item(row, COL_PBSERVER_TAG)
        tag = tagItem.text()

        for k, v in self.edata.setting.syncServers.items():
            server: SyncPublishServer = v
            if col == COL_PBSERVER_TAG and server.tag == tag:
                # item.setText(server.tag)
                QMessageBox.warning(self, "提示", "Tag标识重复了!")
                self.refreshListServer()
                return
            if col == COL_PBSERVER_CAPTION and server.caption == key:
                # item.setText(server.caption)
                QMessageBox.warning(self, "提示", "名字重复了!")
                self.refreshListServer()
                return

        if col == COL_PBSERVER_CAPTION:
            key = self.selectedOldServerKey
        edataItem = self.getServerDictItemByKey(key)
        print(
            "【发布服务器】QTable 原数据：" + edataItem.caption + ", " + edataItem.serverPath + ", " + edataItem.tag)  # + ", " + edataItem.user + ", " + edataItem.pwd
        if col == COL_PBSERVER_CAPTION:
            oldKey = edataItem.caption
            edataItem.caption = keyItem.text()
            del self.edata.setting.syncServers[oldKey]
            self.edata.setting.syncServers[edataItem.caption] = edataItem
            self.refreshListServer()
        else:
            item: QTableWidgetItem = self.ui.listServer.item(row, col)
            if col == COL_PBSERVER_TAG:
                edataItem.tag = item.text()
            if col == COL_PBSERVER_PATH:
                edataItem.serverPath = item.text()
            # if col == COL_PBSERVER_USER:
            #     edataItem.user = item.text()
            # if col == COL_PBSERVER_PWD:
            #     edataItem.pwd = item.text()

        print("【发布服务器】QTable 修改后：" + str(
            edataItem.isEnabledChecked) + ", " + edataItem.caption + ", " + edataItem.serverPath)  # + ", " + edataItem.user + ", " + edataItem.pwd
        self.refreshListFtpServer()

    def refreshListFtpServer(self):
        """下方【发布服务器】表格数据"""
        self.refreshingServerList = True
        self.ui.listFtpServer.setRowCount(len(self.edata.setting.ftpServers))
        count: int = 0
        for (fKey, fData1) in self.edata.setting.ftpServers.items():
            fData: FtpPublishServer = fData1
            # 服务器名
            itemCaption: QTableWidgetItem = QTableWidgetItem(fData.caption)
            # 服务器地址
            itemPath: QTableWidgetItem = QTableWidgetItem(fData.ip)
            # 用户名
            itemUser: QTableWidgetItem = QTableWidgetItem(fData.username)
            # 远端路径
            itemDir: QTableWidgetItem = QTableWidgetItem(fData.remoteDir)

            self.ui.listFtpServer.setItem(count, COL_PBFTP_CAPTION, itemCaption)
            self.ui.listFtpServer.setItem(count, COL_PBFTP_PATH, itemPath)
            self.ui.listFtpServer.setItem(count, COL_PBFTP_USER, itemUser)
            self.ui.listFtpServer.setItem(count, COL_PBFTP_DIR, itemDir)
            count += 1
        self.refreshingServerList = False

    def getFtpServerDictItemByKey(self, searchKey):
        """根据键值找到对应的服务器信息"""
        for key, value in self.edata.setting.ftpServers.items():
            if (key == searchKey):
                return value;

    #########################
    # 工具类函数
    #########################

    def getGenJsonBasePath(self):
        """获得laya Vo生成基础目录，结尾带'\' """
        dirPath: str = Util.getCodePath(self.edata.currentPath, self.edata.setting.defDirJsonFile)
        return dirPath

    def checkAndLoggerHttpResult(self, result: dict):
        """打印网络返回包"""
        if result is None:
            self.logger.logError("网络数据获取失败！")
            return Publish.UPLOAD_COMM_RESULT_ERROR
        objRsult = result.get("result")
        if objRsult is None:
            self.logger.logError("服务器没有消息返回！")
            self.logger.logWarning(str(result))
            return Publish.UPLOAD_COMM_RESULT_ERROR

        resultCode: int = Util.parseInt(objRsult, Publish.UPLOAD_COMM_RESULT_ERROR)
        if resultCode == Publish.UPLOAD_COMM_RESULT_ERROR:
            self.logger.logError("服务器内部错误！")
            return resultCode
        elif resultCode == Publish.LOGIN_RESULT_USER_OR_PASS_WRONG:
            self.logger.logError("登录服务器失败：账号或密码错误！")
            return resultCode
        elif resultCode == Publish.LOGIN_RESULT_NOT_IN_WHITE_LIST:
            self.logger.logError("登录服务器失败：不在白名单！")
            return resultCode
        elif resultCode == Publish.LOGIN_RESULT_REJECT:
            self.logger.logError("登录服务器失败：权限不足！")
            return resultCode
        elif resultCode == Publish.LOGIN_RESULT_NOT_FOUND_TAG:
            self.logger.logError("登录服务器失败：没有对应的服务器！")
            return resultCode
        elif resultCode == Publish.UPLOAD_COMM_ERROR_FORMAT:
            self.logger.logError("上传数据格式错误")
            return resultCode
        elif resultCode == Publish.UPLOAD_COMM_ERROR_EMPTY:
            self.logger.logError("上传数据为空")
            return resultCode
        elif resultCode == Publish.UPLOAD_COMM_ERROR_TOKEN_NOT_FOUND:
            self.logger.logError("传输握手已经过期")
            return resultCode
        elif resultCode == Publish.PUBLISH_RESULT_ERROR:
            self.logger.logError("发布取消！服务器内部错误")
            return resultCode
        elif resultCode == Publish.PUBLISH_RESULT_ERROR_WRONG_DEFINTION:
            self.logger.logError("发布取消！上传的定义数据definition有误！")
            return resultCode
        elif resultCode == Publish.PUBLISH_RESULT_ERROR_WRONG_TABLE:
            self.logger.logError("发布取消！上传含有错误的表格数据")
            return resultCode

        return resultCode
